/*
 * Package il.ac.biu.cs.grossmm.impl.activeData
 * File HashedNode.java
 * 
 * This is a part of presence service framework prototype. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.impl.activeData;

import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.Node;
import il.ac.biu.cs.grossmm.api.data.NodeType;
import il.ac.biu.cs.grossmm.api.data.Nodeset;
import il.ac.biu.cs.grossmm.api.data.Property;
import il.ac.biu.cs.grossmm.api.data.Root;
import il.ac.biu.cs.grossmm.api.data.Unique;
import il.ac.biu.cs.grossmm.api.data.UpdateType;

import java.util.Set;

/**
 * A naive implementation of in-memory data node. This implementation is based
 * on explicit ussage of hash-sets.
 * 
 */
public class HashedNode<N> implements MemNode<N> {
	/**
	 * A 3xN array, where N is number of primitive types (properties) for this
	 * node.
	 * <p>
	 * [i][j] refers to j-th property in:
	 * <ul>
	 * <li> i=0: previous state of the node
	 * <li>i=1: current state of the node
	 * <li>i=2: newly created state of the node (during write-lock is obtained)
	 * </ul>
	 */
	private Object[][] values = new Object[3][];

	/**
	 * Array of subnode-sets
	 */
	private HashedNodeset[] subnodes;

	private NodeType<N> nodeType;

	/**
	 * Update type of the current node
	 * <ul>
	 * <li>updateType[0] refers to the update type of the current node state
	 * <li>updateType[1] refers to the update type of the new node state being
	 * created
	 * </ul>
	 */
	UpdateType[] updateType = new UpdateType[2];

	/**
	 * Refers to the set of subnodes (of the parent node) to which this node
	 * belongs
	 */
	final HashedNodeset parentSet;

	public HashedNode(NodeType<N> nt) {
		this(nt, null);
	}

	public HashedNode(NodeType<N> nt, HashedNodeset parentSet) {
		this.nodeType = nt;
		this.updateType[1] = UpdateType.ADDED;
		int nProps = nt.propertiesNumber();
		int nSets = nt.nodesetsNumber();

		this.parentSet = parentSet;

		for (int i = 1; i < 3; i++)
			values[i] = new Object[nProps];

		if (nSets > 0) {
			subnodes = new HashedNodeset[nSets];

			for (int i = 0; i < nSets; i++)
				subnodes[i] = new HashedNodeset(this, i);
		}
	}

	@SuppressWarnings("unchecked")
	public <P> Root<P> getRoot() {
		return (Root<P>) parentSet.rootNode;
	}

	/**
	 * Shifts epochs
	 */
	public void shift() {
		if (values != null && values[1].length > 0)
			if (values[0] == null) {
				// first update, emulate no-change
				System.arraycopy(values[2], 0, values[1], 0, values[2].length);
				values[0] = values[1].clone();

				// at this stage values[0], values[1] and values[2] all have the
				// same value, moreover values[0] and values[1] is the same
				// array
			} else {
				// all other updates
				shiftValues();
			}

		// shift any modified subnodes in subtree rooted at this node
		if (subnodes != null)
			for (HashedNodeset ns : subnodes)
				ns.shift();

		// shift modification type
		updateType[0] = updateType[1];
		updateType[1] = UpdateType.UNCHANGED;
	}

	/**
	 * Shift of epochs for a node which did not change between the current and
	 * the new epochs but changed between the old and the current epochs
	 * 
	 */
	public void shift2() {
		shiftValues();

		if (subnodes != null)
			for (HashedNodeset ns : subnodes)
				ns.shift2();

		updateType[0] = UpdateType.UNCHANGED;
	}

	/**
	 * Shifts values of the node
	 */
	private void shiftValues() {

		Object[] t = values[0];
		values[0] = values[1];
		System.arraycopy(values[2], 0, t, 0, values[2].length);
		values[1] = t;

		// at this stage values[1] and values[2] have the same value
		// values[0] refers to a copy of old version of values[1]
	}

	@SuppressWarnings("unchecked")
	public <V> void setValue(Property<N, V> pt, V value)
			throws OperationFailedException {

		if (pt instanceof Unique) {
			if (parentSet != null) {
				MemNode parentNode = parentSet.parentNode;
				int parentIndex = parentSet.parentIndex;
				MemNode n2 = parentNode.getSubnode(parentIndex, pt.index(),
						value, 1);
				if (n2 != null && n2 != this)
					throw new OperationFailedException("Constraint violation");
			}
		}

		values[2][pt.index()] = value;

		changed();
	}

	public <V> boolean hasValue(Property<N, V> pt, V value) {
		int index = pt.index();
		Object current = values[2][index];

		if (current == null)
			return value == null;

		if (current == value || current.equals(value))
			return true;

		return false;
	}

	/**
	 * Marks node and all its subnodes removed
	 * 
	 */
	public void remove() {
		if (updateType[1] == UpdateType.REMOVED)
			return;

		if (subnodes != null)
			for (int i = 0; i < subnodes.length; i++) {
				subnodes[i].clear();
			}

		if (parentSet != null)
			parentSet.moveTo(this, UpdateType.REMOVED);

		updateType[1] = UpdateType.REMOVED;
	}

	public void changed() {
		if (updateType[1] == UpdateType.UNCHANGED) {
			if (parentSet != null)
				parentSet.moveTo(this, UpdateType.CHANGED);

			updateType[1] = UpdateType.CHANGED;
		}
	}

	@SuppressWarnings("unchecked")
	public <P> MemNode<P> getParent() {
		if (parentSet == null)
			return null;
		return parentSet.parentNode;
	}

	public NodeType<N> getNodeType() {
		return nodeType;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getValue(il.ac.biu.cs.grossmm.api.data.Property,
	 *      int)
	 */
	@SuppressWarnings("unchecked")
	public <V> V getValue(Property<N, V> property, int epoch) {
		return (V) values[epoch][property.index()];
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getValue(int, int)
	 */
	@SuppressWarnings("unchecked")
	public <V> V getValue(int index, int epoch) {
		return (V) values[epoch][index];
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getSubnodes(il.ac.biu.cs.grossmm.api.data.Nodeset,
	 *      il.ac.biu.cs.grossmm.api.data.UpdateType, int)
	 */
	@SuppressWarnings("unchecked")
	public <S> Set<Node<S>> getSubnodes(Nodeset<N, S> nodeSet, UpdateType u,
			int stage) {
		int index = nodeSet.index();

		Set set = subnodes[index].nodes[stage][u.ordinal()];

		return set;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getSubnodes(int, int)
	 */
	@SuppressWarnings("unchecked")
	public <S> Set<Node<S>> getSubnodes(int index, int stage) {
		HashedNodeset subnodes = this.subnodes[index];
		Set[] sets = (Set[]) subnodes.nodes[stage];
		return new MergedSet<Node<S>>(sets);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getSubnode(int, int, U,
	 *      int)
	 */
	public <N, S, U> MemNode<S> getSubnode(int nIndex, int uIndex, U value,
			int epoch) {
		Set<HashedNode<S>> set = (Set) getSubnodes(nIndex, epoch);

		for (MemNode<S> n : set) {
			Object val = n.getValue(uIndex, epoch + 1);
			if (val == null)
				continue;
			if (val.equals(value))
				return n;
		}

		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#createSubnode(il.ac.biu.cs.grossmm.api.data.Nodeset)
	 */
	public <S> Node<S> createSubnode(Nodeset<N, S> nodeSet) {
		NodeType<S> nt = nodeSet.nestedNodeType();

		HashedNodeset nodes = subnodes[nodeSet.index()];

		HashedNode<S> newNode = new HashedNode<S>(nt, nodes);

		nodes.add(newNode);

		return newNode;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getUpdateType(int)
	 */
	public UpdateType getUpdateType(int epoch) {
		return updateType[epoch];
	}
}
